const conf = require('../../conf/conf.json')
const { Filter } = require("../../display/filters")
const { translateFilters } = require("../../display/filters")
const { Display, translateValue, translateBloon, damageModValue } = require("../../display/general")
const { parseProperties } = require('../../util/enums')
const { ignoreFunction, ignoreIt } = require('../../util/ignore')
const { getModelType } = require('../../util/utils')
const { parseEmission } = require('../emission/emission')
const { parseFilters } = require('../filter/parse')
const { parseBloonBehavior } = require('./bloon')


const models = {}

//----------------------------------------------------
// traveling & lifespan
//----------------------------------------------------

models.AccelerateModel = (b, p) => {
    p.traveling.maxSpeed = b.maxSpeed
    p.traveling.acceleration = b.acceleration
    p.traveling.maxTurnRate = Display.unitAny(b.maxTurnRate, '°/s')
    p.traveling.angularAcceleration = Display.unitAny(b.turnRateChange, '°/s²')
}

models.AcidPoolModel = (b, p) => {
    p.traveling.lifespan = Display.overrideLifespan(p.traveling.lifespan, b.lifespan)
    if (b.lifespanIfMisses) {
        p.meta.acidPool = {}
        p.meta.acidPool.pierce = b.pierce
        p.meta.acidPool.radius = b.radiusIfMisses
        p.meta.acidPool.lifespan = Display.time(b.lifespanIfMisses)
    }
}

models.AdoraTrackTargetModel = (b, p) => {
    p.traveling.minSpeed = b.minimumSpeed
    p.traveling.maxSpeed = b.maximumSpeed
    p.traveling.acceleration = b.acceleration
    p.traveling.angularAcceleration = Display.unitAny(b.accelerateInAngle, '°/s²')
    //p.traveling.decelerateAngularThreshold = b.startDeceleratingIfAngleGreaterThan
    p.traveling.lifespanWhenTracking = Display.time(b.lifespan)
}

models.AgeModel = (b, p) => {
    p.traveling.lifespan = Display.overrideLifespan(p.traveling.lifespan, b.lifespan)
    p.traveling.rounds = b.rounds < 999 ? b.rounds : '∞'
}

models.AgeRandomModel = (b, p) => {
    if (b.minLifespan === b.maxLifespan) {
        p.traveling.lifespan = Display.time(b.minLifespan)
    } else {
        p.traveling.minLifespan = Display.time(b.minLifespan)
        p.traveling.maxLifespan = Display.time(b.maxLifespan)
    }
    p.rounds = b.rounds
}

models.ArriveAtTargetModel = (b, p) => {
    p.traveling.speed = b.altSpeed ? b.altSpeed : null
    p.traveling.timeToArriveAt = b.timeToTake ? Display.time(b.timeToTake) : null
}

models.FallToGroundModel = (b, p) => {
    p.traveling.timeToGround = Display.time(b.timeToTake)
}

models.FollowPathModel = (b, p) => {
    p.traveling.maxDistance = 0
    b.path.forEach(e => {
        // you win
        p.traveling.maxDistance = Math.max(e.y, p.traveling.maxDistance)
    })
    p.traveling.speed = b.speed
}

models.InstantModel = ignoreFunction

models.LightningModel = (b, p) => {
    if (!b.splits) return
    p.meta.split = b.splits
    p.meta.splitRange = b.splitRange
    p.meta.splitDelay = Display.time(b.delay)
}

models.RandomRangeTravelStraitModel = (b, p) => {
    p.traveling.minTravelDistance = b.minRange
    p.traveling.maxTravelDistance = b.maxRange
    p.traveling.speed = b.speed
}

models.SilasTrackTargetModel = (b, p) => {
    p.traveling.seekAngle = Display.unitAny(b.rotation, '°/s')
    p.traveling.minSpeed = b.minimumSpeed
    p.traveling.maxSpeed = b.maximumSpeed
    p.traveling.acceleration = b.acceleration
    p.traveling.travelDelay = Display.time(b.projectileWaitTime)
    p.traveling.lifespan = Display.overrideLifespan(p.traveling.lifespan, b.lifespan)
}

models.TrackManuallyGivenTargetModel = (b, p) => {
    p.traveling.lifespan = Display.time(b.projectileLifeSpan)
}

models.TrackTargetModel = (b, p) => {
    p.traveling.distance = b.distance >= 99999 ? '∞' : b.distance
    p.traveling.seekAngle = b.ignoreSeekAngle ? null : Display.angle(b.maxSeekAngle > 360 ? '∞' : b.maxSeekAngle)
    p.traveling.turnRate = Display.unitAny(b.TurnRate, '°/s')
}
models.TrackTargetWithinTimeModel = models.TrackTargetModel

models.TravelAlongPathModel = (b, p) => {
    p.traveling.speed = b.speed
    p.traveling.lifespan = Display.overrideLifespan(p.traveling.lifespan, b.lifespan)
}

models.TravelCurvyModel = (b, p) => {
    p.turnAngle = Display.angle(b.maxTurnAngle)
    b.turnRate = Display.unitAny(b.turnRate, '°/s')
    p.traveling.speed = b.speed
    p.traveling.lifespan = Display.overrideLifespan(p.traveling.lifespan, b.lifespan)
}

models.TravelStraitModel = (b, p) => {
    p.traveling.speed = b.speed
    p.traveling.lifespan = Display.overrideLifespan(p.traveling.lifespan, b.lifespan)
}
models.TravelTowardsEmitTowerModel = models.TravelStraitModel

models.TravelStraitSlowdownModel = (b, p) => {
    p.traveling.maxDistance = b.maxDistance
    p.traveling.speed = b.speed
    p.traveling.minSpeed = b.minSpeed === b.speed ? null : b.minSpeed
    p.traveling.lifespan = Display.overrideLifespan(p.traveling.lifespan, b.lifespan)
}

//----------------------------------------------------
// damage & pierce
//----------------------------------------------------

models.CollideExtraPierceReductionModel = (b, p) => {
    if (!b.extraAmount) return
    p.piercePenalty.push({
        keyword: b.bloonTag ? translateBloon(b.bloonTag) : translateBloon('all'),
        value: Display.additive(b.extraAmount)
    })
}

models.DamageInRingRadiusModel = (b, p) => {
    p.damage = b.damage
    p.maxDamage = b.maxDamage > 0 && b.maxDamage < 9999999 ? b.maxDamage : null
    p.damageType = b.immuneBloonProperties
    p.meta.innerRingRadius = Display.percent(b.innerRingRadius)
    p.notes.push(
        !b.distributeToChildren ? translateValue('damageCantSoak', 'damage cannot soak through bloons') : null,
        b.overrideDistributeBlocker ? translateValue('damageSoaksBlimps', 'damage can soak through blimps') : null,
    )
}

models.DamageModel = (b, p) => {
    p.damage = b.damage
    p.maxDamage = b.maxDamage > 0 && b.maxDamage < 9999999 ? b.maxDamage : null
    p.damageType = b.immuneBloonProperties
    p.notes.push(
        !b.distributeToChildren ? translateValue('damageCantSoak', 'damage cannot soak through bloons') : null,
        b.overrideDistributeBlocker ? translateValue('damageSoaksBlimps', 'damage can soak through blimps') : null,
        b.ignoreImmunityDestroy ? translateValue('dontDestroyIfNotDamage', 'will not destroy if can not damage') : null,
    )
}

models.DamageModifierForBloonStateModel = (b, p) => {
    if (b.damageMultiplier === 1 && b.damageAdditive === 0) {
        return
    }
    p.damageMods.push({
        keyword: translateValue('damageModsKeywordStatus',
            'status:' + b.bloonStates.map(Display.mutationId).join(b.mustIncludeAllStates ? '+' : '/'),
            b.bloonStates.map(Display.mutationId).join(b.mustIncludeAllStates ? '+' : '/'),),
        value: damageModValue(b.damageAdditive, b.damageMultiplier)
    })
}

models.DamageModifierForBloonTypeModel = (b, p) => {
    if (b.damageMultiplier === 1 && b.damageAdditive === 0) {
        return
    }
    if (b.bloonId === 'MOAB') {
        return
    }
    p.damageMods.push({
        keyword: translateValue('damageModsKeywordBloon', 'bloon:' + b.bloonId, translateBloon(b.bloonId)),
        value: damageModValue(b.damageAdditive, b.damageMultiplier)
    })
}

models.DamageModifierForTagModel = (b, p) => {
    if (b.damageMultiplier === 1 && b.damageAddative === 0) {
        return
    }
    p.damageMods.push({
        keyword: translateValue('damageModsKeywordTag',
            b.tags.join(b.mustIncludeAllTags ? '+' : '/'),
            b.tags.map(Display.tag).join(b.mustIncludeAllTags ? '+' : '/'),
        ),
        value: damageModValue(b.damageAddative, b.damageMultiplier)
    })
}

models.DamageModifierWrathModel = (b, p) => {
    p.meta.additionalDamagePerStack = Display.additive(b.damage)
    p.meta.leakDamagePerStack = b.rbeThreshold
    p.meta.maxAdditionalDamage = Display.additive(b.maxDamageBoost)
}

models.DamagePercentOfMaxModel = (b, p) => {
    if (!b.tags && b.tags.length === 0) {
        return
    }
    p.damageMods.push({
        keyword: translateValue('damageModsKeywordTag', b.tags.join('/'), b.tags.map(translateBloon).join('/')),
        value: Display.percent(b.percent)
    })
}

models.DetonateEffectModel = (b, p) => {
    p.meta.mutationId = Display.mutationId(b.effectMutationIds)
    p.meta.damageIncreasePerSecond = b.damagePerSecondRemaining
}

models.DistributeToChildrenBloonModifierModel = (b, p) => {
    p.notes.push(translateValue('damageSoaksBlimps', 'damage can soak through blimps'))
}

models.IgnoreInsufficientPierceModel = (b, p) => {
    p.filters.push(new Filter('ignoreIfNoEnoughPierce', 'ignore target if pierce not enough'))
}

models.SaudaAfflictionDamageModifierModel = (b, p) => {
    p.damageMods.push({
        keyword: translateValue('damageModsSaudaLv7', b.lv7TagsList.map(Display.tag).join('/')),
        value: Display.additive(b.lv7NonMoabBonus)
    }, {
        keyword: translateValue('damageModsSaudaLv7Blimp', b.lv7TagsList.map(Display.tag).join('/')),
        value: Display.additive(b.lv7MoabBonus)
    })
    if (b.lv11NonMoabBonus) {
        p.damageMods.push({
            keyword: translateValue('damageModsSaudaLv11', `non-${b.lv11ExcludeTagsList.map(Display.tag).join('/')}`),
            value: Display.additive(b.lv11NonMoabBonus)
        }, {
            keyword: translateValue('damageModsSaudaLv11Blimp', `non-${b.lv11ExcludeTagsList.map(Display.tag).join('/')}`),
            value: Display.additive(b.lv11MoabBonus)
        })
    }
    if (b.lv19NonMoabBonus) {
        p.damageMods.push({
            keyword: translateValue('damageModsSaudaLv19', b.lv19BloonTagsList.map(Display.tag).join('/')),
            value: Display.additive(b.lv19NonMoabBonus)
        }, {
            keyword: translateValue('damageModsSaudaLv19Blimp', b.lv19BloonTagsList.map(Display.tag).join('/')),
            value: Display.additive(b.lv19MoabBonus)
        })
    }
}

models.ScaleDamageWithTimeModel = (b, p) => {
    if (!b.scalePerSecond) return
    p.meta.damageScaleBase = Display.multiplier(b.baseDamage)
    p.meta.damageScaleIncreasePerSecond = Display.multiplier(b.scalePerSecond)
    p.meta.damageScaleMax = Display.multiplier(b.maxDamage)
}

//----------------------------------------------------
// sub projectiles
//----------------------------------------------------

models.CreateGreatWhiteEffectModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const projDragging = parseProjectile(b.thrashingProjectileModel)
    projDragging.condition = translateValue('onGreatWhiteDragging', 'dragging')
    projDragging.meta.postDragDelay = Display.time(b.postDragTime)
    projDragging.meta.interval = Display.time(b.thrashingProjectileRate)

    const projLatching = parseProjectile(b.bloonFollowProjectileModel)
    projLatching.condition = translateValue('onGreatWhiteLatching', 'latching')

    p.subProjectiles.push(projDragging, projLatching)
}

models.CreateProjectilesInAreaModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectileModel)
    parseEmission(b.emissionModel, proj)
    proj.condition = translateValue('onInterval', `every ${b.intervalFrames / 60}s`, b.intervalFrames / 60)
    proj.meta.range = b.range
    p.subProjectiles.push(proj)
}

models.CreateProjectileOnBlockerCollideModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('onHitWalls', `on hitting obstacles`)
    p.subProjectiles.push(proj)
}

models.CreateProjectileOnContactModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('onContact', `on contact`)
    p.subProjectiles.push(proj)
}

models.CreateProjectileOnExhaustFractionModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('xPierceUsed', `${b.fraction * 100}% pierce used`, Display.percent(b.fraction))
    p.subProjectiles.push(proj)
}

models.CreateProjectileOnExhaustPierceModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('perXPierceUsed', `every ${b.pierceInterval} pierce used`, b.pierceInterval)
    proj.meta.emissionCount = b.count >= 9999999 ? Infinity : b.count
    proj.collideDelay = b.minimumTimeDifferenceInFrames ? Display.time(b.minimumTimeDifferenceInFrames / 60) : null
    p.subProjectiles.push(proj)
}

models.CreateProjectileOnExpireModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('onExpire', 'on expire')
    p.subProjectiles.push(proj)
}

models.CreateProjectileOnIntervalModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    if (!b.onlyEmitOnce) {
        proj.condition = translateValue('onInterval', `every ${b.intervalFrames / 60}s`, b.intervalFrames / 60)
    } else {
        proj.condition = translateValue('onIntervalOnce', `after ${b.intervalFrames / 60}s`, b.intervalFrames / 60)
    }
    if (b.range > 0) proj.meta.range = b.range
    p.subProjectiles.push(proj)
}

models.CreateProjectilesAlongPathWhenCloseModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    p.subProjectiles.push(proj)
}

models.CreateProjectilesOnTrackOnExpireModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('onExpire', 'on expire')
    proj.emission.count = b.count
    proj.meta.range = b.range
    p.subProjectiles.push(proj)
}

models.CreateProjectilesOnTrackOnExhaustFractionModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('xPierceUsed', `${b.fraction * 100}% pierce used`, Display.percent(b.fraction))
    proj.emission.count = b.amtOfEmissions
    proj.meta.range = b.range
    p.subProjectiles.push(proj)
}

models.EmitOnDamageModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('onDamage', 'on expire')
    p.subProjectiles.push(proj)
}

models.KineticChargeProjectileModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.projectileExplosionModel)
    p.subProjectiles.push(proj)
}

models.ScaleProjectileOverTimeModel = (b, p) => {
    if (!b.bonusProjectileModel) return
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.bonusProjectileModel)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('onExpire', 'onexpire')
    p.subProjectiles.push(proj)
}

models.SpawnZombieOnBloonDestroyedModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.zombieProjectile)
    proj.condition = translateValue('onTargetDestroyed', 'on destroy bloons')
    p.subProjectiles.push(proj)
}

//----------------------------------------------------
// statuses
//----------------------------------------------------

models.AddAcidicMixtureToProjectileModel = (b, p) => {
    const fakeProj = { damageMods: [] }
    b.projBehaviors.forEach(e => parseProjectileBehavior(e, fakeProj))
    p.statuses.push({
        name: translateValue('amd', 'amd'),
        mutationId: Display.mutationId(b.mutationId),
        stacksPerThrow: b.towerBehaviors[0].maxCount,
        stackCap: b.cap,
        damageMods: fakeProj.damageMods
    })
}

models.AddBehaviorModifierForTagModel = (b, p) => {
    p.statuses.forEach(s => {
        if (!s.mutationId.og.startsWith(b.mutatorId) && !s.mutationId.og.startsWith(b.mutatorId + ':')) {
            return
        }
        s['meta.lifespan'][`lifespanFor${b.tag}`] = Display.time(b.mutatorMultiplier * s.lifespan.og)
    })
}

models.AddBehaviorToBloonModel = (b, p) => {
    if (p.statuses.some(s => s.mutationId.og === b.mutationId)) {
        return
    }
    b.behaviors.forEach(e => {
        let res = {
            name: '',
            mutationId: Display.mutationId(b.mutationId),
            filters: [],
            meta: {},
            behaviors: [],
            layers: b.layers >= 9 ? Infinity : b.layers,
            lifespan: Display.time(b.lifespan),
            'meta.lifespan': {},
            notes: []
        }
        const type = getModelType(e)
        if (ignoreIt(type)) return
        parseBloonBehavior(e, res, type)
        res.filters = translateFilters(res.filters.concat(
            b.filter ? parseFilters(b.filter) : [],
            b.filters ? parseFilters(b.filters) : []
        ))
        p.statuses.push(res)
    })
}

models.AddBerserkerBrewToProjectileModel = (b, p) => {
    p.statuses.push({
        name: translateValue('brew', 'brew'),
        mutationId: Display.mutationId(b.mutationId),
        attackCount: b.cap >= 9999999 ? null : b.cap,
        additionalPierce: Display.additive(b.pierceUp),
        additionalDamage: Display.additive(b.damageUp),
        cooldownReductionPercent: Display.multiplierPercent(1 - b.rateUp),
        rangeBonusPercent: Display.additivePercent(b.rangeUp),
        lifespan: b.lifespan > 0 ? Display.time(b.lifespan) : null
    })
}

models.AddBonusDamagePerHitToBloonModel = (b, p) => {
    p.statuses.push({
        name: translateValue('takeMoreDamage', 'takes more damage'),
        mutationId: Display.mutationId(b.mutationId),
        additionalDamage: Display.additive(b.perHitDamageAddition),
        layers: b.layers >= 9 ? Infinity : b.layers,
        lifespan: Display.time(b.lifespan)
    })
}

models.AddTagToBloonModel = (b, p) => {
    p.statuses.push({
        name: translateValue('addTag', 'add tag'),
        mutationId: Display.mutationId(b.mutationId),
        tag: Display.mutationId(b.bloonTag),
        lifespan: Display.time(b.lifespan)
    })
}

models.BloonSlapModel = (b, p) => {
    p.statuses.push({
        name: translateValue('knockback', 'knockback'),
        mutationId: Display.mutationId(b.mutationId),
        speedMultiplier: b.lightMultiplier ? Display.percent(-b.lightMultiplier) : null,
        lifespan: Display.time(b.lifespan)
    })
}

models.DamageUpTagTowersModel = (b, p) => {
    p.statuses.push({
        name: translateValue('damageIncrease', 'damageIncrease'),
        mutationId: Display.mutationId(b.mutatorId),
        tag: Display.mutationId(b.bloonTags.join('/')),
        additionalDamage: Display.additive(b.increase),
        lifespan: Display.time(b.lifespan)
    })
}

models.DamageUpTowersModel = (b, p) => {
    p.statuses.push({
        name: translateValue('damageIncrease', 'damageIncrease'),
        mutationId: Display.mutationId(b.mutatorId),
        additionalDamage: Display.additive(b.increase),
        lifespan: Display.time(b.lifespan)
    })
}

models.FreezeModel = (b, p) => {
    p.statuses.push({
        name: translateValue('freeze', 'freeze'),
        mutationId: Display.mutationId(b.mutationId),
        layers: b.layers >= 9 ? Infinity : b.layers,
        speedMultiplier: Display.multiplier(b.speed),
        meta: {},
        lifespan: Display.time(b.lifespan),
        'meta.lifespan': {}
    })
}

models.FreezeModifierForTagsModel = (b, p) => {
    p.statuses.forEach(s => {
        if (!s.mutationId.og.startsWith(b.freezeId) && !s.mutationId.og.startsWith(b.freezeId + ':')) {
            return
        }
        b.tags.forEach(e => {
            s.meta[`lifespanFor${e}`] = Display.time(b.freezeTimeMultiplier * s.lifespan.og)
        })
    })
}

models.HeatItUpDamageBuffModel = (b, p) => {
    p.statuses.push({
        name: translateValue('heatItUpDamage', 'heat it up damage'),
        mutationId: Display.mutationId(b.mutatorId),
        lifespan: Display.time(b.lifespan)
    })
}

models.IncreaseBloonWorthModel = (b, p) => {
    p.statuses.push({
        name: translateValue('increaseBloonCash', 'increase bloon cash'),
        mutationId: Display.mutationId(b.mutatorId),
        cash: b.cash ? b.cash : null,
        cashMultiplier: b.cashMultiplier === 1 ? null : Display.additivePercent(b.cashMultiplier - 1),
        bloonPropertiesToRemove: parseProperties(b.bloonPropertiesToRemove, conf.translate),
        filters: b.filter ? translateFilters(parseFilters(b.filter)) : null,
        lifespan: Display.time(b.duration)
    })
}

models.IncreaseBloonWorthWithTierModel = (b, p) => {
    p.statuses.push({
        name: translateValue('increaseBloonCash', 'increase bloon cash'),
        mutationId: Display.mutationId(b.mutatorId),
        cashPerTier: b.cashPerTier
    })
}

models.KnockbackModel = (b, p) => {
    p.statuses.push({
        name: translateValue('knockback', 'knockback'),
        mutationId: Display.mutationId(b.mutationId),
        speedMultiplierForBloons: b.lightMultiplier ? Display.percent(-b.lightMultiplier) : null,
        'speedMultiplierForLead/Ceramic': b.heavyMultiplier ? Display.percent(-b.heavyMultiplier) : null,
        speedMultiplierForBlimps: b.moabMultiplier ? Display.percent(-b.moabMultiplier) : null,
        lifespan: Display.time(b.lifespan)
    })
}

models.LatchToBloonModel = (b, p) => {
    p.statuses.push({
        name: translateValue('latching', 'latching'),
        mutationId: Display.mutationId(b.SlowMutator.id),
        speedMultiplier: Display.multiplierPercent(b.bloonSpeedMultiplier),
    })
    p.meta.postLatchDelay = Display.time(b.postBloonDestroyTime)
}

models.PierceUpTowersModel = (b, p) => {
    p.statuses.push({
        name: translateValue('pierceIncrease', 'pierce increase'),
        mutationId: Display.mutationId(b.mutatorId),
        additionalPierce: Display.additive(b.increase),
        lifespan: Display.time(b.lifespan)
    })
}

models.PsiEffectModel = (b, p) => {
    p.statuses.push({
        name: translateValue('psiTremble', 'tremble'),
        damagePerTick: b.damagePerTick,
        interval: Display.time(b.tickRate),
        lifespan: Display.time(b.maxDuration)
    })
    if (b.projectileOnBloonDestroyModel) {
        const { parseProjectile } = require('./parse')
        const proj = parseProjectile(b.projectileOnBloonDestroyModel)
        proj.condition = translateValue('onTargetDestroyed', 'on destroy bloons')
        p.subProjectiles.push(proj)
    }
}

models.PsiPsionicScreamModel = (b, p) => {
    if (b.psiEffectChance < 1) {
        p.statuses.push({
            name: translateValue(`blowback`, `blow back`),
            minDistance: b.distractDistanceMin,
            maxDistance: b.distractDistanceMax,
            chance: Display.percent(1 - b.psiEffectChance),
            speedMultiplier: Display.multiplier(b.speedMultiplier),
        })
    }
}

models.RemoveDamageTypeModifierModel = (b, p) => {
    p.statuses.push({
        name: translateValue('removeImmunity', 'remove immune'),
        mutationId: Display.mutationId(b.mutationId),
        bloonPropertiesToRemove: parseProperties(b.bloonPropertiesToRemove, conf.translate),
        layers: b.layers >= 9 ? Infinity : b.layers,
        lifespan: Display.time(b.lifespan)
    })
}

models.SilasApplyRimeModel = (b, p) => {
    p.statuses.push({
        name: translateValue('rime', 'rime'),
        mutationId: Display.mutationId(b.rimeMutationId),
        speedMultiplier: Display.multiplierPercent(b.speedModifier),
        speedMultiplierForBlimps: Display.multiplierPercent(b.moabSpeedModifier)
    }, {
        name: translateValue('freeze', 'freeze'),
        mutationId: Display.mutationId(b.freezeMutationId),
        layers: b.freezeLayers,
        lifespan: Display.time(b.freezeLifespan)
    })
    const { parseProjectile } = require('./parse')
    const proj = parseProjectile(b.explosionProjectileModel)
    parseEmission(b.explosionEmissionModel, proj, null)
    p.subProjectiles.push(proj)
}

models.SlowForBloonModel = (b, p) => {
    const bloons = new Set((b.bloonIds ?? []).concat(b.bloonTags ?? []))
    if (!p.statuses.some(s => {
        if (s.mutationId.og !== b.mutationId)
            return false
        for (let bloon of bloons) {
            s['meta.speedMultiplier'][`speedMultiplierFor${b.excluding ? 'Non' : ''}${bloon}`] = Display.multiplier(b.Multiplier)
            s['meta.lifespan'][`lifespanFor${b.excluding ? 'Non' : ''}${bloon}`] = Display.time(b.Lifespan)
        }
        return true
    })) {
        const result = {
            name: translateValue('slow', `slow${!b.dontRefreshDuration ? ' with duration refresh' : ''}`),
            mutationId: Display.mutationId(b.mutationId),
            'meta.speedMultiplier': {},
            layers: b.layers >= 9 ? Infinity : b.layers,
            glueLevel: b.glueLevel === 0 ? null : b.glueLevel,
            'meta.lifespan': {}
        }
        for (let bloon of bloons) {
            result['meta.speedMultiplier'][`speedMultiplierFor${b.excluding ? 'Non' : ''}${bloon}`] = Display.multiplier(b.Multiplier)
            result['meta.lifespan'][`lifespanFor${b.excluding ? 'Non' : ''}${bloon}`] = Display.time(b.Lifespan)
        }
        p.statuses.push(result)
    }
}

models.SlowMaimMoabModel = (b, p) => {
    const arr = ['moab', 'bfb', 'zomg', 'ddt', 'bad']
    arr.forEach(e => {
        if (b[e + 'Duration']) {
            p.statuses.push({
                name: translateValue('maimMOAB', `${b.bloonPerHitDamageAddition > 0 ? 'cripple' : 'maim'} MOAB(${e})`, e.toUpperCase()),
                mutationId: Display.mutationId(b[e + 'Mutator'].id),
                speedMultiplier: e !== 'bad' ? Display.multiplier(b.multiplier) : null,
                additionalDamage: b.bloonPerHitDamageAddition ? Display.additive(b.bloonPerHitDamageAddition) : null,
                lifespan: Display.time(b[e + 'Duration']),
            })
        }
    })
}

models.SlowMinusAbilityDurationModel = (b, p) => {
    p.statuses.push({
        name: translateValue('slow', 'slow during the ability'),
        mutationId: Display.mutationId(b.mutationId),
        speedMultiplier: Display.multiplier(b.Multiplier),
        lifespan: Display.time(b.Lifespan),
        'meta.lifespan': {}
    })
}

models.SlowModel = (b, p) => {
    p.statuses.push({
        name: translateValue('slow', `slow${!b.dontRefreshDuration ? ' with duration refresh' : ''}`),
        mutationId: Display.mutationId(b.mutationId),
        speedMultiplier: Display.multiplier(b.Multiplier),
        meta: {},
        layers: b.layers >= 9 ? Infinity : b.layers,
        glueLevel: b.glueLevel === 0 ? null : b.glueLevel,
        lifespan: Display.time(b.lifespan),
        'meta.lifespan': {}
    })
}

models.SlowModifierForTagModel = (b, p) => {
    if (!b.slowId || b.tag === 'Miniboss' || !b.tag) return
    p.statuses.forEach(s => {
        if (!s.mutationId.og.startsWith(b.slowId) && !s.mutationId.og.startsWith(b.slowId + ':')) {
            return
        }
        if (b.resetToUnmodified || b.preventMutation) {
            s['meta.lifespan'][`lifespanFor${b.tag}`] = Display.time(0)
            return
        }
        if (b.lifespanOverride) {
            s['meta.lifespan'][`lifespanFor${b.tag}`] = Display.time(b.lifespanOverride)
        }
        if (b.slowMultiplier) {
            s.meta[`speedMultiplierFor${b.makeNotTag ? 'Non' : ''}${b.tag}`] = Display.multiplier(b.slowMultiplier)
        }
    })
}

models.SlowOnPopModel = (b, p) => {
    p.statuses.push({
        name: translateValue('slowOnPop', `slow on pop${!b.dontRefreshDuration ? ' with duration refresh' : ''}`),
        mutationId: Display.mutationId(b.mutationId),
        speedMultiplier: Display.multiplier(b.Multiplier),
        layers: b.layers >= 9 ? Infinity : b.layers,
        glueLevel: b.glueLevel === 0 ? null : b.glueLevel,
        lifespan: Display.time(b.Lifespan),
    })
}

models.StripChildrenModel = (b, p) => {
    p.statuses.push({
        name: translateValue('stripChildren', 'strip children'),
        mutationId: Display.mutationId(b.Mutator.id)
    })
}

models.TranceBloonModel = (b, p) => {
    p.statuses.push({
        name: translateValue('trance', 'trance'),
        mutationId: Display.mutationId(b.Mutator.id),
        orbitRadius: b.orbitRadius,
        bloonOrbitSpeedMultiplier: Display.multiplier(b.speedMultiplier),
        blimpOrbitSpeedMultiplier: Display.multiplier(b.moabOrbitSpeed),
        maxDotDamage: b.maxDotDamage,
        damageType: b.fakeDamage.immuneBloonProperties,
        lifespan: Display.time(b.duration)
    })
}

models.WindModel = (b, p) => {
    if (!b.chance) return
    let result = {
        name: translateValue(`blowback${b.affectMoab ? '' : 'Bloons'}`, `blow back${b.affectMoab ? '' : ' bloons'}`),
        mutationId: Display.mutationId(b.mutator.id),
        minDistance: b.distanceMin,
        maxDistance: b.distanceMax,
        'meta.distance': {},
        chance: b.chance === 1 ? null : Display.percent(b.chance),
        speedMultiplier: Display.multiplier(b.speedMultiplier),
    }
    b.distanceScaleForTagsTagsList.forEach(e => {
        result['meta.distance'][`distanceScaleFor${e}`] = Display.multiplier(b.distanceScaleForTags)
    })
    p.statuses.push(result)
}

//----------------------------------------------------
// other
//----------------------------------------------------

models.AddHeatToBloonModel = ignoreFunction

models.BrickellFreezeOtherMinesModel = (b, p) => {
    p.meta.minHitTime = Display.time(b.freezeDuration)
}

models.CashModel = (b, p) => {
    if (b.minimum === 0 && b.maximum === 0) return
    if (b.minimum === b.maximum) {
        p.meta.cash = b.minimum
    } else {
        p.meta.cashRange = [b.minimum, b.maximum]
    }
    p.meta.cashBonusMultiplier = b.bonusMultiplier ? Display.additivePercent(b.bonusMultiplier) : null
    p.meta.cashSalvageRate = b.salvage ? Display.percent(b.salvage) : null
}

models.ClearHitBloonsModel = (b, p) => {
    p.meta.rehitInterval = Display.time(b.interval)
}

models.ClearHitBloonsWhenNoLongerCollidingModel = (b, p) => {
    // TODO: parse this thing
}

models.CollideOnlyWithTargetModel = ignoreFunction

models.CreateTowerModel = (b, p) => {
    const { parseTower } = require('../tower/parse')
    p.meta.subTowers = parseTower(b.tower)
}

models.CreateTypedTowerModel = (b, p) => {
    const { parseTower } = require('../tower/parse')
    p.meta.subTowers = [
        parseTower(b.crushingTower),
        parseTower(b.boomTower),
        parseTower(b.coldTower),
        parseTower(b.energyTower),
    ]
}

models.DelayBloonChildrenSpawningModel = (b, p) => {
    p.meta.spawnChildrenDelay = Display.time(b.lifespan)
}

models.DestroyIfTargetLostModel = (b, p) => {
    p.notes.push(translateValue('destroyProjectileWhenTargetLost', 'destroy when target lost'))
}

models.DestroyProjectileIfTowerDestroyedModel = (b, p) => {
    p.notes.push(translateValue('destroyProjectileWhenTowerDestroyed', 'destroy when tower destroyed'))
}

models.EatBloonModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    p.meta.rbeCap = b.rbeCapacity
    p.meta.cashMultiplier = Display.additivePercent(b.rbeCashMultiplier - 1)
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
    proj.condition = translateValue('rbeCapReached', 'rbe cap reached')
    proj.meta.cashRange = null
    p.subProjectiles.push(proj)
}

models.ExpireProjectileAtScreenEdgeModel = (b, p) => {
    p.notes.push(translateValue('expireAtMapBorder', 'expires at screen edge'))
}

models.ExpireProjectileOnParentDestroyedModel = (b, p) => {
    p.notes.push(translateValue('destroyProjectileWhenTowerDestroyed', 'destroy when tower destroyed'))
}

models.IgnoreCollisionAfterCollideModel = (b, p) => {
    p.meta.collideDelay = Display.time(b.timeAfterCollide)
}

models.JungleVineEffectModel = (b, p) => {
    p.meta.jungleVinePostDestroyDelay = Display.time(b.destroyAfterPopTime)
}

models.LivesModel = (b, p) => {
    if (b.minimum === b.maximum) {
        p.meta.lives = b.minimum
    } else {
        p.meta.liveRange = [b.minimum, b.maximum]
    }
}

models.MapBorderReboundModel = (b, p) => {
    p.notes.push(b.clearCollidedWith ?
        translateValue('canBounceOffMapBordersAndRehit', `can bounce off map borders and rehit after rebound`) :
        translateValue('canBounceOffMapBorders', `can bounce off map borders`))
}

models.MoabTakedownModel = (b, p) => {
    p.meta.cashMultiplier = b.increaseMoabBloonWorth ? Display.multiplier(b.multiplier - 1) : null
}

models.MorphBloonModel = ignoreFunction // let that be RED

models.PickupModel = (b, p) => {
    p.meta.pickRadius = b.collectRadius
}

models.ProjectileBlockerCollisionReboundModel = (b, p) => {
    p.notes.push(
        b.clearCollidedWith ?
            translateValue('canBounceOffWallsAndRehit', 'can bounce off walls and rehit after rebound') :
            translateValue('canBounceOffWalls', 'can bounce off walls'))
}

models.PushBackModel = (b, p) => {
    if (b.tag !== 'Moabs') {
        p.meta.bloonPushBackDistance = b.pushAmount
    }
    p.meta.moabPushBackDistance = b.multiplierMOAB ? b.pushAmount * b.multiplierMOAB : null
    p.meta.bfbPushBackDistance = b.multiplierBFB ? b.pushAmount * b.multiplierBFB : null
    p.meta.zomgPushBackDistance = b.multiplierZOMG ? b.pushAmount * b.multiplierZOMG : null
    p.meta.ddtPushBackDistance = b.multiplierDDT ? b.pushAmount * b.multiplierDDT : null
}

models.RefreshPierceModel = (b, p) => {
    p.meta.pierceRefreshInterval = Display.time(b.interval)
}

models.RemoveBloonModifiersModel = (b, p) => {
    if (!b.cleanseCamo && !b.cleanseRegen && !b.cleanseFortified && !b.cleanseLead) {
        return
    }
    p.meta['removeProperties' + (b.cleanseOnlyIfDamaged ? 'OnDamage' : '')] =
        ([
            b.cleanseCamo ? 'camo' : '',
            b.cleanseRegen ? 'grow' : '',
            b.cleanseFortified ? 'fortified' : '',
            b.cleanseLead ? 'lead' : '',
        ]).filter(e => e !== '').map(translateBloon).join('/')
    p.meta.cantRemovePropertiesTo = b.bloonTagExcludeList.filter(e => e !== 'false').map(translateBloon).join('/')
    p.meta.canOnlyRemovePropertiesTo = b.bloonTagExplicitList.filter(e => e !== 'false').map(translateBloon).join('/')
    if (!p.meta.cantRemovePropertiesTo) p.meta.cantRemovePropertiesTo = null
    if (!p.meta.canOnlyRemovePropertiesTo) p.meta.canOnlyRemovePropertiesTo = null
}

models.RemoveMutatorsFromBloonModel = (b, p) => {
    p.meta.removeStatus = b.mutatorIdList.map(Display.mutationId)
}

models.RetargetOnContactModel = (b, p) => {
    p.meta.bounceDistance = b.distance
    p.meta.minBounceDistance = b.minDistance > 0 ? b.minDistance : null
    p.meta.maxBounces = b.maxBounces < 9999999 ? b.maxBounces : null
    p.meta.bounceDelay = b.delay > 0 ? Display.time(b.delay) : null
}

models.WallOfTreesModel = (b, p) => {
    const { parseProjectile } = require('./parse')
    p.meta.rbeCap = b.rbeCapacity
    p.meta.cashMultiplier = b.rbeCashMultiplier
    const proj = parseProjectile(b.projectile)
    parseEmission(b.emission, proj, null)
}

models.ProjectileFilterModel = ignoreFunction

function parseProjectileBehavior(b, p, type = null) {
    if (!type) type = getModelType(b)
    if (models[type]) models[type](b, p)
    else p.behaviors.push(b)
}

module.exports = {
    parseProjectileBehavior
}